import sys
sys.path.append("..")

from Controller.topo_gen import *
from CoreSim.Flow import *
from CoreSim.Event import *
from CoreSim.Unit import *
from CoreSim.Scheduler import *
import networkx as nx

# Visualization
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.lines as mlines
import matplotlib.patches as mpatches

HOSTPORT = 0
TORPORT = 1

inDir = "Input/"
outDir = "Output/"

def getOracleFCT(flow, bandwidth):
    pd = PROPAGATION_DELAY
    td = (float(flow.flowSize) / bandwidth) # seconds
    return pd, td

def getSortedFlows(activeFlows):
    sortedActiveFlows = sorted(activeFlows, key=lambda flow: flow.remainTime)
    return sortedActiveFlows

# 3-stage clos network
class Port(object):
    """docstring for Port"""
    def __init__(self, id, port_type, torId, portNum):
        self.id = id
        self.type = port_type
        self.torId = torId
        self.portNum = portNum
        self.pre_hop = 0  # dst ingress port --> point to pre torID, dst ingress port point to egress port
        self.next_hop = 0 # src ingress port --> point to egress port, src egress port point to dest torID
        self.flow = 0 # flows running on this port

        # matplotlib
        # rectangle
        self.xy = (0,0)
        self.width = 0
        self.height = 0
        self.linewidth = 1
        self.fill = False

    def __repr__(self):
        return 'port_id:{!r}, torId:{!r}, port_type:{!r}'.format(self.id, self.torId, self.type)

class Host(object):
    """docstring for ClassName"""
    def __init__(self, id, torId):
        self.id = id
        self.torId = torId
        self.flow = 0 # running flow
        self.backupFlows = [] # backup flows
        self.bundles = [] # bundle level

        # matplotlib
        # rectangle
        self.xy = (0,0)
        self.width = 0
        self.height = 0
        self.linewidth = 1
        self.fill = False

    def __repr__(self):
        return 'host_id:{!r}, torId:{!r}, {!r}'.format(self.id, self.torId, self.flow)

class ToR(object):
    """docstring for ToRs"""
    def __init__(self, torId, numHosts, numLinks):
        self.id = torId
        self.hostPorts = {}
        self.torPorts = {}

        for x in xrange(1,numHosts+1):
            self.hostPorts[x] = Port((torId-1)*(numLinks+numHosts)+x, HOSTPORT, torId, x)
        for x in xrange(1,numLinks+1):
            self.torPorts[x] = Port((torId-1)*(numLinks+numHosts)+numHosts+x, TORPORT, torId, x)

        # matplotlib
        # rectangle
        self.xy = (0,0)
        self.width = 0
        self.height = 0
        self.linewidth = 1
        self.fill = False
    
    def __repr__(self):
        return 'tor_id:{!r}'.format(self.id)

def get_torID(hostId, numHosts):
    torId = (hostId-1)/numHosts + 1
    portNum = (hostId-1)%numHosts + 1
    return torId, portNum

def get_hostID(torId, portNum, numHosts):
    hostId = (torId-1)*numHosts + portNum
    return hostId

class SchedulerDelay(Scheduler):
    def __init__(self, numHosts, numSwitches, numLinks, bandwidth):
        Scheduler.__init__(self)

        self.numHosts = numHosts
        self.numLinks = numLinks
        self.numSwitches = numSwitches
        self.bandwidth = bandwidth

        self.srcHosts = {}
        self.dstHosts = {}
        self.srcToRs = {}
        self.dstToRs = {}
        self.midToRs = {} # used for visualization
        
        self.visual_enalbe = False

        # initial
        for torId in xrange(1,numSwitches+1):
            self.srcToRs[torId] = ToR(torId=torId, numHosts=numHosts, numLinks=numLinks)
            self.dstToRs[torId] = ToR(torId=torId, numHosts=numHosts, numLinks=numLinks)
            for host in xrange(1,numHosts+1):
                self.srcHosts[(torId-1)*numHosts+host] = Host((torId-1)*numHosts+host, torId)
                self.dstHosts[(torId-1)*numHosts+host] = Host((torId-1)*numHosts+host, torId)
        for torID in xrange(1,numLinks+1):
            self.midToRs[torID] = ToR(torId=torId, numHosts=numSwitches, numLinks=numSwitches)

        # Initial the ToR Connection (k-regular)
        bp_src = range (1,numSwitches+1)
        bp_dst = range (numSwitches+1, numSwitches*2+1)
        regular_graph = nx.random_regular_graph(numLinks, numSwitches) # node id starts from 0
        while not nx.is_connected(regular_graph):
            regular_graph = nx.random_regular_graph(numLinks, numSwitches)
        matches = bipartite_matchings(regular_graph, bp_src, bp_dst, numLinks, numSwitches)
        
        for portId in matches:
            print portId, matches[portId]
            for src,dst in matches[portId]:
                self.srcToRs[src].torPorts[portId].next_hop = dst-numSwitches
                self.dstToRs[dst-numSwitches].torPorts[portId].pre_hop = src
        
        if self.visual_enalbe:
            self.Visualization_Init()

    def UpdateFlow(self, curFlow, eventType, eventQueue, curTime):
        # type: (object, object, object, object) -> object
        
        if eventType == FlowArrival:
            prop_delay, trans_delay = getOracleFCT(curFlow, self.bandwidth)
            curFlow.remainTime = trans_delay
            curFlow.oracleFct = trans_delay
            self.runningFlows.append(curFlow)
        
        elif eventType == FlowFinished:
            curFlow.finishTime = curTime
            # Update Host and ToR Status
            src_hostID = curFlow.startId
            dst_hostID = curFlow.endId
            src_torID, src_portNum = get_torID(src_hostID, self.numHosts)
            dst_torID, dst_portNum = get_torID(dst_hostID, self.numHosts)
            torPortNum = self.srcToRs[src_torID].hostPorts[src_portNum].next_hop
            self.srcHosts[src_hostID].flow = 0
            self.dstHosts[dst_hostID].flow = 0
            self.srcToRs[src_torID].torPorts[torPortNum].flow = 0
            self.dstToRs[dst_torID].torPorts[torPortNum].flow = 0
            # Update Flows
            self.runningFlows.remove(curFlow)
            self.finishedFlows.append(curFlow)
        else:
            assert False

        if self.visual_enalbe:
            self.Visualization_Run("Event%s Begin"%(eventType))
        
        # Traversing the inprogress flows
        # Cancel the finishevent
        for host in self.srcHosts.values():
            if host.flow != 0:
                inProgressFlow = host.flow
                if inProgressFlow.updateTime == 0:
                    print "flow in progress without LastTime set"
                    assert False
                if inProgressFlow.finishEvent == 0:
                    print "flow in progress without FinishEvent set"
                    assert False

                inProgressFlow.remainTime -= (curTime - inProgressFlow.updateTime)
                inProgressFlow.updateTime = 0

                inProgressFlow.finishEvent.cancelled = True
                inProgressFlow.finishEvent = 0
        
        sortedActiveFlows = getSortedFlows(self.runningFlows)

        counter = 0
        for flow in sortedActiveFlows:
            src_hostID = flow.startId
            dst_hostID = flow.endId
            src_torID, src_hostPortNum = get_torID(src_hostID, self.numHosts)
            dst_torID, dst_hostPortNum = get_torID(dst_hostID, self.numHosts)
            srcToR = self.srcToRs[src_torID]
            dstToR = self.dstToRs[dst_torID]

            counter += 1
            if self.visual_enalbe:
                self.Visualization_Run("Round%s Before! src%s dst%s"%(counter, src_hostID, dst_hostID), src_hostID, dst_hostID)

            # Haven't been preempted, the flow itself
            if self.srcHosts[src_hostID].flow == flow and self.dstHosts[dst_hostID].flow == flow:
                print "case0 -----itself!"
                flow.updateTime = curTime
                flow.finishEvent = FlowFinishedEvent(curTime+flow.remainTime, flow)
                eventQueue.push(flow.finishEvent, curTime+flow.remainTime)
                continue

            '''
            For the src ToR, point to the next hop ToR of the bestPortNum
            For the dst ToR, point to the pre hop ToR of the bestPortNum
            They may be connected by the bestPortNum
            '''
            bestPortNum = 0
            if self.srcHosts[src_hostID].flow == 0 and self.dstHosts[dst_hostID].flow == 0: # src and dst hosts are free

                bestPortNum = self.FindBestPort(srcToR, dstToR, flow)
                print "case1", bestPortNum
                if bestPortNum == 0:
                    continue
                else:
                    src_next_torID = srcToR.torPorts[bestPortNum].next_hop
                    dst_pre_torID = dstToR.torPorts[bestPortNum].pre_hop

                    if src_next_torID == dst_torID:
                        print "111111"
                        if srcToR.torPorts[bestPortNum].flow != 0:
                            pre_src_hostID = get_hostID(srcToR.id, srcToR.torPorts[bestPortNum].pre_hop, self.numHosts)
                            next_hostID = get_hostID(dstToR.id, dstToR.torPorts[bestPortNum].next_hop, self.numHosts)
                            self.srcHosts[pre_src_hostID].flow = 0
                            self.dstHosts[next_hostID].flow = 0
                    else:
                        print "222222"
                        src_next_tor = self.dstToRs[src_next_torID]
                        dst_pre_tor = self.srcToRs[dst_pre_torID]
                        if srcToR.torPorts[bestPortNum].flow != 0:
                            pre_src_hostID = get_hostID(srcToR.id, srcToR.torPorts[bestPortNum].pre_hop, self.numHosts)
                            next_hostID = get_hostID(src_next_torID, src_next_tor.torPorts[bestPortNum].next_hop, self.numHosts)
                            self.srcHosts[pre_src_hostID].flow = 0
                            self.dstHosts[next_hostID].flow = 0

                            pre_dst_hostID = get_hostID(dstToR.id, dstToR.torPorts[bestPortNum].next_hop, self.numHosts)
                            pre_hostID = get_hostID(dst_pre_tor.id, dst_pre_tor.torPorts[bestPortNum].pre_hop, self.numHosts)
                            self.srcHosts[pre_hostID].flow = 0
                            self.dstHosts[pre_dst_hostID].flow = 0
                        
                        dst_pre_tor.torPorts[bestPortNum].next_hop = src_next_torID
            
            elif self.srcHosts[src_hostID].flow != 0 and self.dstHosts[dst_hostID].flow != 0:
                print "case2"
                if self.srcHosts[src_hostID].flow == self.dstHosts[dst_hostID].flow:
                    print "case2 -----1"
                    if flow.remainTime < self.srcHosts[src_hostID].flow.remainTime:
                        bestPortNum = srcToR.hostPorts[src_hostPortNum].next_hop
                    else:
                        continue
                else:
                    if flow.remainTime >= min(self.srcHosts[src_hostID].flow.remainTime, self.dstHosts[dst_hostID].flow.remainTime):
                        continue
                    else:
                        src_next_hostID = self.srcHosts[src_hostID].flow.endId
                        src_next_torID, src_next_hostPortNum = get_torID(src_next_hostID, self.numHosts)
                        src_next_tor = self.dstToRs[src_next_torID]
                        src_torPortNum = srcToR.hostPorts[src_hostPortNum].next_hop

                        dst_pre_hostID = self.dstHosts[dst_hostID].flow.startId
                        dst_pre_torID, dst_pre_hostPortNum = get_torID(dst_pre_hostID, self.numHosts)
                        dst_pre_tor = self.srcToRs[dst_pre_torID]
                        dst_torPortNum = dstToR.hostPorts[dst_hostPortNum].pre_hop

                        if src_next_torID == dst_torID:
                            print "case2 -----2"
                            bestPortNum = src_torPortNum
                        
                        elif dst_pre_torID == src_torID:
                            print "case2 -----3"
                            bestPortNum = dst_torPortNum
                        
                        else:
                            print "case2 -----4"
                            # find a empty and connected link
                            for portNum in srcToR.torPorts:
                                if srcToR.torPorts[portNum].next_hop == dst_torID and srcToR.torPorts[portNum].flow == 0:
                                    print "case2 -----51: exsit links and no flows running"
                                    bestPortNum = portNum
                                    break

                            # find a preemptive port
                            if bestPortNum == 0:
                                if dstToR.hostPorts[dst_hostPortNum].pre_hop == srcToR.hostPorts[src_hostPortNum].next_hop:
                                    print "case2 -----52"
                                    bestPortNum = dstToR.hostPorts[dst_hostPortNum].pre_hop
                                else:
                                    print "case2 -----53"
                                    #assert False
                                    continue

                        self.dstHosts[src_next_hostID].flow = 0
                        src_next_tor.torPorts[src_torPortNum].flow = 0
                        src_next_tor.torPorts[src_torPortNum].next_hop = 0
                        src_next_tor.hostPorts[src_next_hostPortNum].pre_hop = 0
                        srcToR.torPorts[src_torPortNum].flow = 0
                        srcToR.torPorts[src_torPortNum].pre_hop = 0

                        self.srcHosts[dst_pre_hostID].flow = 0
                        dst_pre_tor.torPorts[dst_torPortNum].flow = 0
                        dst_pre_tor.torPorts[dst_torPortNum].pre_hop = 0
                        dst_pre_tor.hostPorts[dst_pre_hostPortNum].next_hop = 0
                        dstToR.torPorts[dst_torPortNum].flow = 0
                        dstToR.torPorts[dst_torPortNum].next_hop = 0

            elif self.srcHosts[src_hostID].flow != 0:
                print "case3"
                if flow.remainTime >= self.srcHosts[src_hostID].flow.remainTime:
                    print "case3 -----1"
                    continue
                else:
                    print "case3 -----2"

                    src_next_hostID = self.srcHosts[src_hostID].flow.endId
                    src_next_torID, src_next_hostPortNum = get_torID(src_next_hostID, self.numHosts)
                    src_next_tor = self.dstToRs[src_next_torID]
                    src_torPortNum = srcToR.hostPorts[src_hostPortNum].next_hop

                    if src_next_torID == dst_torID:
                        bestPortNum = src_torPortNum
                    else:
                        # find a empty and connected link
                        for portNum in srcToR.torPorts:
                            if srcToR.torPorts[portNum].next_hop == dst_torID and srcToR.torPorts[portNum].flow == 0:
                                print "case2 -----51: exsit links and no flows running"
                                bestPortNum = portNum
                                break

                        # find a preemptive port
                        if bestPortNum == 0:
                            if dstToR.hostPorts[dst_hostPortNum].pre_hop == srcToR.hostPorts[src_hostPortNum].next_hop:
                                print "case2 -----52"
                                bestPortNum = dstToR.hostPorts[dst_hostPortNum].pre_hop
                            else:
                                print "case2 -----53"
                                #assert False
                                continue

                    self.dstHosts[src_next_hostID].flow = 0
                    src_next_tor.torPorts[src_torPortNum].flow = 0
                    src_next_tor.torPorts[src_torPortNum].next_hop = 0
                    src_next_tor.hostPorts[src_next_hostPortNum].pre_hop = 0
                    srcToR.torPorts[src_torPortNum].flow = 0
                    srcToR.torPorts[src_torPortNum].pre_hop = 0
            
            elif self.dstHosts[dst_hostID].flow != 0:
                print "case4"
                if flow.remainTime >= self.dstHosts[dst_hostID].flow.remainTime:
                    print "case4 -----1"
                    continue
                else:
                    print "case4 -----2"

                    dst_pre_hostID = self.dstHosts[dst_hostID].flow.startId
                    dst_pre_torID, dst_pre_hostPortNum = get_torID(dst_pre_hostID, self.numHosts)
                    dst_pre_tor = self.srcToRs[dst_pre_torID]
                    dst_torPortNum = dstToR.hostPorts[dst_hostPortNum].pre_hop

                    if dst_pre_torID == src_torID:
                        bestPortNum = dst_torPortNum
                    else:
                        # find a empty and connected link
                        for portNum in srcToR.torPorts:
                            if srcToR.torPorts[portNum].next_hop == dst_torID and srcToR.torPorts[portNum].flow == 0:
                                print "case2 -----51: exsit links and no flows running"
                                bestPortNum = portNum
                                break

                        # find a preemptive port
                        if bestPortNum == 0:
                            if dstToR.hostPorts[dst_hostPortNum].pre_hop == srcToR.hostPorts[src_hostPortNum].next_hop:
                                print "case2 -----52"
                                bestPortNum = dstToR.hostPorts[dst_hostPortNum].pre_hop
                            else:
                                print "case2 -----53"
                                #assert False
                                continue

                    self.srcHosts[dst_pre_hostID].flow = 0
                    dst_pre_tor.torPorts[dst_torPortNum].flow = 0
                    dst_pre_tor.torPorts[dst_torPortNum].pre_hop = 0
                    dst_pre_tor.hostPorts[dst_pre_hostPortNum].next_hop = 0
                    dstToR.torPorts[dst_torPortNum].flow = 0
                    dstToR.torPorts[dst_torPortNum].next_hop = 0
            else:
                assert False

            # Update the flow
            flow.updateTime = curTime
            self.srcHosts[src_hostID].flow = flow
            self.dstHosts[dst_hostID].flow = flow
            srcToR.torPorts[bestPortNum].flow = flow
            dstToR.torPorts[bestPortNum].flow = flow
            
            # Update the ToR inner connections
            self.srcToRs[src_torID].hostPorts[src_hostPortNum].next_hop = bestPortNum
            self.dstToRs[dst_torID].hostPorts[dst_hostPortNum].pre_hop = bestPortNum

            self.srcToRs[src_torID].torPorts[bestPortNum].pre_hop = src_hostPortNum
            self.dstToRs[dst_torID].torPorts[bestPortNum].next_hop = dst_hostPortNum

            # Update the ToR inter connections
            self.srcToRs[src_torID].torPorts[bestPortNum].next_hop = dst_torID
            self.dstToRs[dst_torID].torPorts[bestPortNum].pre_hop = src_torID

            if flow.finishEvent != 0:
                print "flow being scheduled, finish event non-nil"
                assert False
            flow.finishEvent = FlowFinishedEvent(curTime+flow.remainTime, flow)
            eventQueue.push(flow.finishEvent, curTime+flow.remainTime)
            if self.visual_enalbe:
                self.Visualization_Run("Event%s round%s After!"%(eventType, counter))

    def FindBestPort(self, srcToR, dstToR, preemptive_flow):
        '''
        preemptive strategies
        find the best port to preemptive
        '''
        bestPortNum = 0
        best_remainTime = preemptive_flow.remainTime
        
        for portNum in srcToR.torPorts:
            srcPort = srcToR.torPorts[portNum]
            dstPort = dstToR.torPorts[portNum]
            if srcPort.next_hop == dstToR.id:
                if srcPort.flow == 0:
                    print "-----1: exsit links and no flows running"
                    return portNum
                elif srcPort.flow.remainTime > preemptive_flow.remainTime:
                    print "-----2: exsit links, preemptive the running flow"
                    return portNum
                else:
                    pass
        
        for portNum in srcToR.torPorts:
            srcPort = srcToR.torPorts[portNum]
            dstPort = dstToR.torPorts[portNum]

            if srcPort.flow == 0 and dstPort.flow == 0:
                return portNum

            elif srcPort.flow != 0 and dstPort.flow == 0:
                if srcPort.flow.remainTime > best_remainTime:
                    best_remainTime = srcPort.flow.remainTime
                    bestPortNum = portNum

            elif srcPort.flow == 0 and dstPort.flow != 0:
                if dstPort.flow.remainTime > best_remainTime:
                    best_remainTime = dstPort.flow.remainTime
                    bestPortNum = portNum

            else: # both srcPort and destPort are busy
                if min(srcPort.flow.remainTime, dstPort.flow.remainTime) > best_remainTime:
                    best_remainTime = min(srcPort.flow.remainTime, dstPort.flow.remainTime)
                    bestPortNum = portNum
        return bestPortNum

    def Visualization_Init(self, port_fraction = 0.2):
        '''
        print the circuit switch connections
        '''
        fig, ax = plt.subplots(figsize=(8,6), dpi=80)
        port_enable = True
        # -------- ToR and Port Rectangle --------------------
        torIDx = [0.2, 0.8]
        torIDy = np.linspace(0, 1, self.numSwitches+2)
        torWidthx = 0.04
        torWidthy = (torIDy[1]-torIDy[0])/3
        portWidthx = port_fraction * torWidthy/self.numHosts
        portWidthy = port_fraction * torWidthy/self.numHosts
        for torID in xrange(1,self.numSwitches+1):
            self.srcToRs[torID].xy = (torIDx[0]-torWidthx, torIDy[-torID-1]-torWidthy)
            self.dstToRs[torID].xy = (torIDx[-1]-torWidthx, torIDy[-torID-1]-torWidthy)
            self.srcToRs[torID].width = 2*torWidthx
            self.dstToRs[torID].width = 2*torWidthx
            self.srcToRs[torID].height = 2*torWidthy
            self.dstToRs[torID].height = 2*torWidthy

            srcToR = mpatches.Rectangle(self.srcToRs[torID].xy, self.srcToRs[torID].width, self.srcToRs[torID].height, fill=False)
            dstToR = mpatches.Rectangle(self.dstToRs[torID].xy, self.dstToRs[torID].width, self.dstToRs[torID].height, fill=False)
            ax.add_patch(srcToR)
            ax.add_patch(dstToR)
            # src sw host ports
            for portID in self.srcToRs[torID].hostPorts:
                port = self.srcToRs[torID].hostPorts[portID]
                xy_x = self.srcToRs[torID].xy[0] - portWidthx
                xy_y = self.srcToRs[torID].xy[1] + self.srcToRs[torID].height - self.srcToRs[torID].height*portID/(self.numHosts+1) - portWidthy
                port.xy = (xy_x, xy_y)
                port.width = 2*portWidthx
                port.height = 2*portWidthy
                if port_enable:                    
                    portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                    ax.add_patch(portRect)
            # src sw tor ports
            for portID in self.srcToRs[torID].torPorts:
                port = self.srcToRs[torID].torPorts[portID]
                xy_x = self.srcToRs[torID].xy[0] + self.srcToRs[torID].width - portWidthx
                xy_y = self.srcToRs[torID].xy[1] + self.srcToRs[torID].height - self.srcToRs[torID].height*portID/(self.numLinks+1) - portWidthy
                port.xy = (xy_x, xy_y)
                port.width = 2*portWidthx
                port.height = 2*portWidthy 
                if port_enable:                
                    portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                    ax.add_patch(portRect)
            # dst sw host ports
            for portID in self.dstToRs[torID].torPorts:
                port = self.dstToRs[torID].torPorts[portID]
                xy_x = self.dstToRs[torID].xy[0] - portWidthx
                xy_y = self.dstToRs[torID].xy[1] + self.dstToRs[torID].height - self.dstToRs[torID].height*portID/(self.numLinks+1) - portWidthy
                port.xy = (xy_x, xy_y)
                port.width = 2*portWidthx
                port.height = 2*portWidthy
                if port_enable:                
                    portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                    ax.add_patch(portRect)
            # dst sw tor ports
            for portID in self.dstToRs[torID].hostPorts:
                port = self.dstToRs[torID].hostPorts[portID]
                xy_x = self.dstToRs[torID].xy[0] + self.dstToRs[torID].width - portWidthx
                xy_y = self.dstToRs[torID].xy[1] + self.dstToRs[torID].height - self.dstToRs[torID].height*portID/(self.numHosts+1) - portWidthy
                port.xy = (xy_x, xy_y)
                port.width = 2*portWidthx
                port.height = 2*portWidthy
                if port_enable: 
                    portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                    ax.add_patch(portRect)
        
        # -------- Middle Stage Circuit Switch ---------------
        torIDx = [0.5]
        torIDy = np.linspace(0, 1, self.numLinks+2)
        torWidthx = 0.08
        torWidthy = (torIDy[1]-torIDy[0])/3
        portWidthx = port_fraction * torWidthy/self.numHosts
        portWidthy = port_fraction * torWidthy/self.numHosts
        for torID in xrange(1,self.numLinks+1):
            xy = (torIDx[0]-torWidthx, torIDy[-torID-1]-torWidthy)
            width = 2*torWidthx
            height = 2*torWidthy

            self.midToRs[torID].xy = xy
            self.midToRs[torID].width = width
            self.midToRs[torID].height = height

            middleToR = mpatches.Rectangle(xy, width, height, fill=False)
            ax.add_patch(middleToR)

            # middle tor ingress ports
            for portID in self.midToRs[torID].hostPorts:
                port = self.midToRs[torID].hostPorts[portID]
                xy_x = self.midToRs[torID].xy[0] - portWidthx
                xy_y = self.midToRs[torID].xy[1] + self.midToRs[torID].height - self.midToRs[torID].height*portID/(self.numSwitches+1) - portWidthy
                port.xy = (xy_x, xy_y)
                port.width = 2*portWidthx
                port.height = 2*portWidthy   
                if port_enable:                  
                    portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                    ax.add_patch(portRect)
            # middle tor egress ports
            for portID in self.midToRs[torID].torPorts:
                port = self.midToRs[torID].torPorts[portID]
                xy_x = self.midToRs[torID].xy[0] + self.midToRs[torID].width - portWidthx
                xy_y = self.midToRs[torID].xy[1] + self.midToRs[torID].height - self.midToRs[torID].height*portID/(self.numSwitches+1) - portWidthy
                port.xy = (xy_x, xy_y)
                port.width = 2*portWidthx
                port.height = 2*portWidthy
                if port_enable:               
                    portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                    ax.add_patch(portRect)

        # ---Src, Dst and Middle ToR Connections
        for torID in xrange(1,self.numSwitches+1):
            for portID in self.srcToRs[torID].torPorts:
                src_torPort = self.srcToRs[torID].torPorts[portID]
                dst_torPort = self.dstToRs[torID].torPorts[portID]
                mid_ToR = self.midToRs[portID]
                mid_torPort1 = mid_ToR.hostPorts[torID]
                mid_torPort2 = mid_ToR.torPorts[torID]

                x = (src_torPort.xy[0]+src_torPort.width, mid_torPort1.xy[0])
                y = (src_torPort.xy[1]+src_torPort.height/2, mid_torPort1.xy[1]+mid_torPort1.height/2)
                line = mlines.Line2D(x, y, color='black', linewidth=1)
                ax.add_line(line)

                x = (mid_torPort2.xy[0]+mid_torPort2.width, dst_torPort.xy[0])
                y = (mid_torPort2.xy[1]+mid_torPort2.height/2, dst_torPort.xy[1]+dst_torPort.height/2)
                line = mlines.Line2D(x, y, color='black', linewidth=1)
                ax.add_line(line)
                
                # --------middle ToR inner connections
                dst_torID = src_torPort.next_hop
                mid_torPort2 = mid_ToR.torPorts[dst_torID]
                x = (mid_torPort1.xy[0]+mid_torPort1.width, mid_torPort2.xy[0])
                y = (mid_torPort1.xy[1]+mid_torPort1.height/2, mid_torPort2.xy[1]+mid_torPort2.height/2)
                line = mlines.Line2D(x, y, color='black', linewidth=1)
                ax.add_line(line)
        
        for hostID in self.srcHosts:
            torID, portNum = get_torID(hostID, self.numHosts)
            port = self.srcToRs[torID].hostPorts[portNum]
            ax.text(port.xy[0]-2.5*port.width, port.xy[1]-port.height, '%s'%hostID, ha="center", size=12)
            port = self.dstToRs[torID].hostPorts[portNum]
            ax.text(port.xy[0]+3*port.width, port.xy[1]-port.height, '%s'%hostID, ha="center", size=12)

        plt.subplots_adjust(left=0, right=1, bottom=0, top=1)
        plt.axis('equal')
        #plt.axis('off')
        plt.show()
    
    def Visualization_Run(self, text = 'none', src_HostID=0, dst_HostID=0):
        '''
        print the circuit switch connections
        '''
        fig, ax = plt.subplots(figsize=(8,6), dpi=80)
        ax.text(0.5, self.midToRs[self.numLinks].xy[1]-0.04, text, ha="center", size=14)
        # -------- ToR and Port Rectangle --------------------
        for torID in xrange(1,self.numSwitches+1):
            srcToR = mpatches.Rectangle(self.srcToRs[torID].xy, self.srcToRs[torID].width, self.srcToRs[torID].height, fill=False)
            dstToR = mpatches.Rectangle(self.dstToRs[torID].xy, self.dstToRs[torID].width, self.dstToRs[torID].height, fill=False)
            ax.add_patch(srcToR)
            ax.add_patch(dstToR)
            # src sw host ports
            for portID in self.srcToRs[torID].hostPorts:
                port = self.srcToRs[torID].hostPorts[portID]
                portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                ax.add_patch(portRect)

                port = self.dstToRs[torID].hostPorts[portID]
                portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                ax.add_patch(portRect)
            # src sw tor ports
            for portID in self.srcToRs[torID].torPorts:
                port = self.srcToRs[torID].torPorts[portID]             
                portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                ax.add_patch(portRect)
                
                port = self.dstToRs[torID].torPorts[portID]
                portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                ax.add_patch(portRect)
        # -------- Middle Stage Circuit Switch ---------------
        for torID in xrange(1,self.numLinks+1):

            middleToR = mpatches.Rectangle(self.midToRs[torID].xy, self.midToRs[torID].width, self.midToRs[torID].height, fill=False)
            ax.add_patch(middleToR)

            # middle tor ingress ports
            for portID in self.midToRs[torID].hostPorts:
                port = self.midToRs[torID].hostPorts[portID]                
                portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                ax.add_patch(portRect)
            # middle tor egress ports
            for portID in self.midToRs[torID].torPorts:
                port = self.midToRs[torID].torPorts[portID]            
                portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='black')
                ax.add_patch(portRect)

        # --- Src, Dst and Middle ToR Connections --------------
        for torID in xrange(1,self.numSwitches+1):
            for portID in self.srcToRs[torID].torPorts:
                src_torPort = self.srcToRs[torID].torPorts[portID]
                dst_torPort = self.dstToRs[torID].torPorts[portID]
                mid_ToR = self.midToRs[portID]
                mid_torPort1 = mid_ToR.hostPorts[torID]
                mid_torPort2 = mid_ToR.torPorts[torID]

                x = (src_torPort.xy[0]+src_torPort.width, mid_torPort1.xy[0])
                y = (src_torPort.xy[1]+src_torPort.height/2, mid_torPort1.xy[1]+mid_torPort1.height/2)
                line = mlines.Line2D(x, y, color='black', linewidth=1)
                ax.add_line(line)

                x = (mid_torPort2.xy[0]+mid_torPort2.width, dst_torPort.xy[0])
                y = (mid_torPort2.xy[1]+mid_torPort2.height/2, dst_torPort.xy[1]+dst_torPort.height/2)
                line = mlines.Line2D(x, y, color='black', linewidth=1)
                ax.add_line(line)
                
                # --------middle ToR inner connections
                dst_torID = src_torPort.next_hop
                mid_torPort2 = mid_ToR.torPorts[dst_torID]
                x = (mid_torPort1.xy[0]+mid_torPort1.width, mid_torPort2.xy[0])
                y = (mid_torPort1.xy[1]+mid_torPort1.height/2, mid_torPort2.xy[1]+mid_torPort2.height/2)
                line = mlines.Line2D(x, y, color='black', linewidth=1)
                ax.add_line(line)

        # flow connections
        for hostID in self.srcHosts:
            torID, portNum = get_torID(hostID, self.numHosts)
            port = self.srcToRs[torID].hostPorts[portNum]
            ax.text(port.xy[0]-2.5*port.width, port.xy[1]-port.height, '%s'%hostID, ha="center", size=12)
            port = self.dstToRs[torID].hostPorts[portNum]
            ax.text(port.xy[0]+3*port.width, port.xy[1]-port.height, '%s'%hostID, ha="center", size=12)
            
            if self.srcHosts[hostID].flow != 0:
                flow = self.srcHosts[hostID].flow
                src_hostID = flow.startId
                dst_hostID = flow.endId
                src_torID, src_portNum = get_torID(src_hostID, self.numHosts)
                dst_torID, dst_portNum = get_torID(dst_hostID, self.numHosts)

                # Src ToR Inner Connection
                pre_port = self.srcToRs[src_torID].hostPorts[src_portNum]
                next_port = self.srcToRs[src_torID].torPorts[pre_port.next_hop]
                x = (pre_port.xy[0]+pre_port.width, next_port.xy[0])
                y = (pre_port.xy[1]+pre_port.height/2, next_port.xy[1]+next_port.height/2)
                line = mlines.Line2D(x, y, color='r', linewidth=1)
                ax.add_line(line)

                # Src and Middle ToR Inter Connection
                midToRId = pre_port.next_hop
                pre_port = next_port
                next_port = self.midToRs[midToRId].hostPorts[src_torID]
                x = (pre_port.xy[0]+pre_port.width, next_port.xy[0])
                y = (pre_port.xy[1]+pre_port.height/2, next_port.xy[1]+next_port.height/2)
                line = mlines.Line2D(x, y, color='r', linewidth=1)
                ax.add_line(line)

                # Middle ToR Inner Connection
                pre_port = next_port
                next_port = self.midToRs[midToRId].torPorts[dst_torID]
                x = (pre_port.xy[0]+pre_port.width, next_port.xy[0])
                y = (pre_port.xy[1]+pre_port.height/2, next_port.xy[1]+next_port.height/2)
                line = mlines.Line2D(x, y, color='r', linewidth=1)
                ax.add_line(line)

                # Middle and Dst ToR Inter Connection
                pre_port = next_port
                next_port = self.dstToRs[dst_torID].torPorts[midToRId]
                x = (pre_port.xy[0]+pre_port.width, next_port.xy[0])
                y = (pre_port.xy[1]+pre_port.height/2, next_port.xy[1]+next_port.height/2)
                line = mlines.Line2D(x, y, color='r', linewidth=1)
                ax.add_line(line)
                
                # Dst ToR Inner Connection
                pre_port = next_port
                next_port = self.dstToRs[dst_torID].hostPorts[pre_port.next_hop]
                x = (pre_port.xy[0]+pre_port.width, next_port.xy[0])
                y = (pre_port.xy[1]+pre_port.height/2, next_port.xy[1]+next_port.height/2)
                line = mlines.Line2D(x, y, color='r', linewidth=1)
                ax.add_line(line)

        # preemptive src and dst
        if src_HostID != 0 and  dst_HostID != 0:
            src_torID, src_portNum = get_torID(src_HostID, self.numHosts)
            dst_torID, dst_portNum = get_torID(dst_HostID, self.numHosts)
            
            port = self.srcToRs[src_torID].hostPorts[src_portNum]
            portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='r')
            ax.add_patch(portRect)

            port = self.dstToRs[dst_torID].hostPorts[dst_portNum]
            portRect = mpatches.Rectangle(port.xy, port.width, port.height, color='r')
            ax.add_patch(portRect)


        plt.subplots_adjust(left=0, right=1, bottom=0, top=1)
        plt.axis('equal')
        #plt.axis('off')
        plt.show()
